﻿namespace GreenIsomer.Data.Entities.Ddl {
	using System;
	using System.Xml;
	using System.IO;
	using System.Globalization;

	internal class SsdlInput {

		internal SsdlInput() { }

		internal XmlDocument Document { get; private set; }

		internal XmlNamespaceManager NamespaceManager { get; private set; }

		internal void Load(string baseDirectory, string file) {

			if(string.IsNullOrEmpty(file))
				throw new InvalidOperationException("SSDL input file not specified.");

			string path = Generator.ResolvePath(baseDirectory, file);

			try {

				Document = new XmlDocument();

				using(TextReader reader = new StreamReader(path))
					Document.Load(reader);

				NamespaceManager = new XmlNamespaceManager(Document.NameTable);
				NamespaceManager.AddNamespace("ssdl", Generator.SsdlNamespace);
				NamespaceManager.AddNamespace("store", Generator.SsdlStoreNamespace);
				NamespaceManager.AddNamespace("gidex", Generator.GidexNamespace);

			} catch(FileNotFoundException ex) {
				Document = null;
				NamespaceManager = null;
				throw new FileNotFoundException(string.Format(CultureInfo.InvariantCulture,
					"SSDL input file '{0}' not found.", path), ex);
			} catch(DirectoryNotFoundException ex) {
				Document = null;
				NamespaceManager = null;
				throw new DirectoryNotFoundException(string.Format(CultureInfo.InvariantCulture,
					"SSDL input file directory '{0}' not found.", Path.GetDirectoryName(path)), ex);
			} catch(IOException ex) {
				Document = null;
				NamespaceManager = null;
				throw new IOException(string.Format(CultureInfo.InvariantCulture,
					"SSDL input file '{0}' I/O error.", path), ex);
			} catch(Exception) {
				Document = null;
				NamespaceManager = null;
				throw;
			}
		}

		internal TableCollection GetTables() {

			TableCollection tables = new TableCollection();

			foreach(XmlNode entitySetNode in Document.SelectNodes("//ssdl:EntitySet", NamespaceManager)) {

				XmlAttribute storeTypeAttribute = entitySetNode.Attributes["Type", Generator.SsdlStoreNamespace];
				if(storeTypeAttribute == null || storeTypeAttribute.Value != "Tables") continue;

				XmlAttribute entitySetNameAttribute = entitySetNode.Attributes["Name"];
				if(entitySetNameAttribute == null || string.IsNullOrEmpty(entitySetNameAttribute.Value)) continue;

				XmlAttribute entitySetSchemaAttribute = entitySetNode.Attributes["Schema"];

				XmlNode entityTypeNode = Document.SelectSingleNode(string.Format(CultureInfo.InvariantCulture,
					"//ssdl:EntityType[@Name=\"{0}\"]", entitySetNameAttribute.Value), NamespaceManager);
				if(entityTypeNode == null) continue;

				XmlAttribute entityTypeNameAttribute = entityTypeNode.Attributes["Name"];
				if(entityTypeNameAttribute == null || string.IsNullOrEmpty(entityTypeNameAttribute.Value)) continue;

				Table table = new Table();
				table.Name = entityTypeNameAttribute.Value;
				table.Schema = entitySetSchemaAttribute == null ? null : entitySetSchemaAttribute.Value;

				GetColumns(table, entityTypeNode);
				GetPrimaryKey(table, entityTypeNode);

				tables.Add(table);
			}

			GetForeignKeys(tables);

			return tables;
		}

		private void GetColumns(Table table, XmlNode entityTypeNode) {

			foreach(XmlNode propertyNode in entityTypeNode.SelectNodes("ssdl:Property", NamespaceManager)) {

				XmlAttribute propertyNameAttribute = propertyNode.Attributes["Name"];
				if(propertyNameAttribute == null || string.IsNullOrEmpty(propertyNameAttribute.Value)) continue;

				XmlAttribute propertyTypeAttribute = propertyNode.Attributes["Type"];
				if(propertyTypeAttribute == null || string.IsNullOrEmpty(propertyTypeAttribute.Value)) continue;

				TableColumn column = new TableColumn();

				column.Name = propertyNameAttribute.Value;
				column.DataType = propertyTypeAttribute.Value;

				XmlAttribute propertyOriginalTypeAttribute = propertyNode.Attributes["gidex:OriginalType"];
				column.OriginalDataType = propertyOriginalTypeAttribute == null ? null : propertyOriginalTypeAttribute.Value;

				XmlAttribute propertyMaxLengthAttribute = propertyNode.Attributes["MaxLength"];
				short size;
				if(propertyMaxLengthAttribute != null && short.TryParse(propertyMaxLengthAttribute.Value, out size))
					column.Size = size;

				//TODO: Implement Scale/Precision

				table.Columns.Add(column);
			}

		}

		private void GetPrimaryKey(Table table, XmlNode entityTypeNode) {

			XmlNode keyNode = entityTypeNode.SelectSingleNode("ssdl:Key", NamespaceManager);
			if(keyNode == null) return;

			PrimaryKey primaryKey = new PrimaryKey();
			primaryKey.Name = "PK_" + table.Name;
			//TODO: Implement primary key naming override since EDMX doesn't seem to store it

			foreach(XmlNode propertyRefNode in keyNode.SelectNodes("ssdl:PropertyRef", NamespaceManager)) {

				XmlAttribute nameAttribute = propertyRefNode.Attributes["Name"];
				if(nameAttribute == null) continue;

				ColumnRef column = new ColumnRef();
				column.Name = nameAttribute.Value;

				primaryKey.Columns.Add(column);
			}

			if(primaryKey.Columns.Count != 0) table.PrimaryKey = primaryKey;
		}

		private void GetForeignKeys(TableCollection tables) {

			foreach(XmlNode associationNode in Document.SelectNodes("//ssdl:Association", NamespaceManager)) {

				XmlAttribute nameAttribute = associationNode.Attributes["Name"];
				if(nameAttribute == null || string.IsNullOrEmpty(nameAttribute.Value)) continue;

				XmlNode principalNode = associationNode.SelectSingleNode("ssdl:ReferentialConstraint/ssdl:Principal", NamespaceManager);
				if(principalNode == null) continue;

				XmlAttribute principalRoleAttribute = principalNode.Attributes["Role"];
				if(principalRoleAttribute == null || string.IsNullOrEmpty(principalRoleAttribute.Value)) continue;

				XmlNode dependentNode = associationNode.SelectSingleNode("ssdl:ReferentialConstraint/ssdl:Dependent", NamespaceManager);
				if(dependentNode == null) continue;

				XmlAttribute dependentRoleAttribute = dependentNode.Attributes["Role"];
				if(dependentRoleAttribute == null || string.IsNullOrEmpty(dependentRoleAttribute.Value)) continue;

				Table table = tables.Find(dependentRoleAttribute.Value);
				if(table == null) continue;

				Table referencedTable = tables.Find(principalRoleAttribute.Value);
				if(referencedTable == null) continue;

				ForeignKey foreignKey = new ForeignKey();
				foreignKey.Name = nameAttribute.Value;

				foreach(XmlNode dependentPropertyRefNode in dependentNode.SelectNodes("ssdl:PropertyRef", NamespaceManager)) {

					XmlAttribute dependentRefNameAttribute = dependentPropertyRefNode.Attributes["Name"];
					if(dependentRefNameAttribute == null || string.IsNullOrEmpty(dependentRefNameAttribute.Value)) continue;

					ColumnRef column = new ColumnRef();
					column.Name = dependentRefNameAttribute.Value;

					foreignKey.Columns.Add(column);
				}

				TableRef tableRef = new TableRef();
				foreignKey.ReferencedTable = tableRef;
				tableRef.Name = referencedTable.Name;
				tableRef.Schema = referencedTable.Schema;

				foreach(XmlNode principalPropertyRefNode in principalNode.SelectNodes("ssdl:PropertyRef", NamespaceManager)) {

					XmlAttribute propertyRefNameAttribute = principalPropertyRefNode.Attributes["Name"];
					if(propertyRefNameAttribute == null || string.IsNullOrEmpty(propertyRefNameAttribute.Value)) continue;

					ColumnRef column = new ColumnRef();
					column.Name = propertyRefNameAttribute.Value;

					tableRef.Columns.Add(column);
				}

				if(foreignKey.Columns.Count == 0 || foreignKey.ReferencedTable.Columns.Count == 0) continue;

				table.ForeignKeys.Add(foreignKey);
			}

		}

		internal ProviderSettings GetReferencedProviderSettings(ProviderSettingsCollection providerSettings) {

			XmlNode schemaNode = Document.SelectSingleNode("//ssdl:Schema", NamespaceManager);
			if(schemaNode == null) return null;

			XmlAttribute schemaProviderAttribute = schemaNode.Attributes["Provider"];
			if(schemaProviderAttribute == null || string.IsNullOrEmpty(schemaProviderAttribute.Value))
				return null;

			return providerSettings.Find(schemaProviderAttribute.Value);
		}

	}

}